﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Windows;
using FFmpegVideoClip.Configuration;
using FFmpegVideoClip.Lib;
using FFmpegVideoClip.Lib.AdvancedFragmentProcessor;
using FFmpegVideoClip.Lib.ClipGroups;
using MediaInfoLib;
using Microsoft.Win32;
using Microsoft.WindowsAPICodePack.Dialogs;
using PropertyChanged;
using MediaElement = Unosquare.FFME.MediaElement;

namespace FFmpegVideoClip
{
    [AddINotifyPropertyChangedInterface]
    public static class ClipManage
    {
        public static JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
        {
            IncludeFields = true,
            AllowTrailingCommas = true,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
            NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals,
            ReferenceHandler = ReferenceHandler.IgnoreCycles,
            WriteIndented = false,
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            //Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
        };
        public static string? OldSavePath;
        public static MediaElement? CurrentMediaElement { get; set; }
        public static ObservableCollection<Clip> ClipList { get; private set; }
        public static Fragment CurrentEditFragment { get; private set; } = new();
        public static int TotalFragmentCount=> ClipList.SelectMany(clip=>clip.Fragments).Count();
        public static int ClipCount => ClipList.Count;
        public static int NewFragmentID() => FragmentIDs.Count() < 1 ? 1 : FragmentIDs.Max() + 1;
        public static IEnumerable<int> FragmentIDs => AllFragmentEnumerable.Select(p => p.ID);
        public static IEnumerable<Fragment> AllFragmentEnumerable => ClipList.SelectMany(p => p.Fragments);
        public static Memory<Fragment> AllFragment
        {
            get
            {
                Fragment[] fragments = new Fragment[ClipList.Sum(p => p.Fragments.Count)];
                int i = 0;
                foreach (Clip item in ClipList)
                {
                    item.Fragments.CopyTo(fragments, i);
                    i += item.Fragments.Count;
                }
                return fragments.ToArray();
            }
        }
        
        static ClipManage()
        {
            ClipList = new();
            ClipList.CollectionChanged += ClipList_CollectionChanged;
            //ClipList.Add(new Clip() 
            //{
            //    Name = "Name",
            //    OutDirectory="c:\\",
            //});
        }

        private static void ClipList_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            foreach (Clip item in ClipList)
                item.UpdateIndexUI();
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            {
                foreach (Clip clip in e.OldItems?.Cast<Clip>() ?? [])
                {
                    if (clip.Group != null)
                        ClipsGroupManage.GroupDelet(clip);
                }
            }
        }

        public static Fragment? FindFragment(int id) => ClipList.SelectMany(p => p.Fragments).FirstOrDefault(p => p.ID == id);
        public static int FindClipIndex(Clip clip) => ClipList.IndexOf(clip);
        public static bool ClipListContains(Clip clip) => ClipList.Contains(clip);
        public static void DuplicateNameCheck()
        {
            while (ClipList.Count > 1 && ClipList.GroupBy(p => p.Name, StringComparer.OrdinalIgnoreCase).Any(p => p.Count() > 1))
                ClipList.GroupBy(p => p.Name, StringComparer.OrdinalIgnoreCase).Where(p => p.Count() > 1).ForEach(p => p.Last().Name += "(重复)");
        }

        public static void ClipListCreateFragmentIndex()
        {
            foreach (Clip clip in ClipList)
            {
                for (int i = 0; i < clip.Fragments.Count; i++)
                {
                    clip.Fragments[i].Index = i;
                }
            }
        }

        public static void LoadClipList()
        {
            static bool CheckTime(string FullFilePath, Fragment fragment)
            {
                return Tools.GetStringTimeToTimeSpan(fragment.EndTimeValue) - TimeSpan.FromSeconds(1) > GetMediaInfo.GetInfo(FullFilePath).Duration;
            }
            static bool CheckPickMediaFile(string FullFilePath, Clip clip, Fragment fragment,Dictionary<string,string> Dic)
            {
                try
                {
                    if (CheckTime(FullFilePath, fragment))
                    {
                        MessageBoxEx.Show(Application.Current.MainWindow, $"剪辑[{clip.Name}]片段[{fragment.Name}]的结束时间点大于选取媒体的时长，无法使用该媒体，请从新选择", "媒体异常", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.OK);
                        return false;
                    }
                    Dic[fragment.InputPath] = FullFilePath;
                    fragment.InputPath = FullFilePath;
                    return true;
                }
                catch
                {
                    MessageBoxEx.Show(Application.Current.MainWindow, $"该文件无法获取媒体信息，请从新选择", "读取失败", MessageBoxButton.OK, MessageBoxImage.Warning);
                    return false;
                }
            }

            OpenFileDialog ofd = Tools.GetOpenFileDialog(OldSavePath!, "打开一个剪辑编辑", "*.cli", "剪辑信息文档(.cli)|*.cli");
            if (ofd.ShowDialog() ?? false)
            {
                try
                {
                    OldSavePath = Path.GetDirectoryName(ofd.FileName);
                    SaveData? saveData = JsonSerializer.Deserialize<SaveData>(File.ReadAllText(ofd.FileName), options: SerializerOptions);
                    IEnumerable<Clip>? clips = saveData?.ClipsData.Select(p => p.ToClip()).ToArray();

                    if (clips is null || !clips.Any())
                    {
                        MessageBoxEx.Show(Application.Current.MainWindow, "该保存中没有有效的剪辑", "读取失败", MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    //验证
                    StringBuilder sb = new();
                    Queue<Fragment> DiscardFragments = new();
                    OpenFileDialog? ofdx = null;
                    Dictionary<string, string> dic = new();
                    clips.ForEach(clip =>
                    {
                        if(DiscardFragments.Count > 0)
                            DiscardFragments.Clear();

                        clip.Fragments.ForEach(fragment =>
                        {
                            if (string.IsNullOrEmpty(fragment.InputPath))
                            {
                                sb.AppendLine($"异常项：缺少输入路径,该项将被丢弃:剪辑[{clip.Name}]片段[{fragment.Name}]");
                                DiscardFragments.Enqueue(fragment);
                                return;
                            }
                            if (!File.Exists(fragment.InputPath))
                            {
                                //查看之前是否有针对同一个源进行过成功的更换，如果存在指定的新源
                                //用新源进行时长测试，如果测试失败，进入到拾取更换阶段
                                //重新指定成功后，记录中的源更新到新指定的源，抛弃上一次指定的记录
                                //因为保存的文件中所记录的同一个地址，无法判断那次指定是正确的
                                //所以记录最新一次的指定，作为指定记录
                                if (dic.ContainsKey(fragment.InputPath))
                                { 
                                    string LibPath  =  dic[fragment.InputPath];
                                    if(CheckTime(LibPath,fragment))
                                    {
                                        fragment.clip = clip;
                                        return;
                                    }
                                }
                                //该片段在保存文件记录中的路径，不存在对应的文件（移动或已删除）时允许在该过程中重新指定
                                //在上一个过程中，对于存在过指定记录的源，如果通过时长测试，则不会进入到当前过程
                                //该过程完成从新指定源，且通过时长测试后，换源成功，本次指定的路径信息记录在[dic]中
                                //下一次再次发现某个片段中保存的是相同的，缺失对应文件的路径，则先在上一个过程从[dic]中搜索换源记录
                                //并且能够成功通过时长测试，则不再进入该过程
                                if (MessageBoxEx.Show(Application.Current.MainWindow, $"剪辑[{clip.Name}]片段[{fragment.Name}]:源文件丢失，已被转义或删除，您可以重写指定源，如果放弃重新指定，" +
                                    $"则该片段将被丢弃，是否从新指定源", "异常项", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.OK) == MessageBoxResult.OK)
                                {
                                    if (ofdx is null)
                                        ofdx = Tools.GetOpenFileDialog(Path.GetDirectoryName(fragment.InputPath)!, "选择媒体文件", "*.*", "媒体文件(.*)|*.*");
                                    
                                    while(ofdx.ShowDialog() ?? false)
                                    {
                                        if (CheckPickMediaFile(ofdx.FileName, clip, fragment,dic))
                                        {
                                            fragment.clip = clip;
                                            return;
                                        }
                                    }
                                }
                                sb.AppendLine($"异常项：源文件丢失,该项将被丢弃:剪辑[{clip.Name}]片段[{fragment.Name}]");
                                DiscardFragments.Enqueue(fragment);
                                return;
                            }
                            fragment.clip = clip;
                        });
                        while(DiscardFragments.Count > 0)
                            clip.Fragments.Remove(DiscardFragments.Dequeue());
                    });
                    ClipList.Clear();
                    clips?.ForEach(clip => ClipList.Add(clip));
                    if (sb.Length > 1)
                        MessageBoxEx.Show(Application.Current.MainWindow, $"载入后的异常项:\r\n{sb.ToString()}", "存在异常片段", MessageBoxButton.OK, MessageBoxImage.Warning);
                    Tools.CheckFragmentID();
                    UpdateClipsSchemeModule();
                    ClipsGroupManage.LoadFromClipManage(saveData?.GroupsData);
                }
                catch (Exception exc)
                {
                    MessageBoxEx.Show(Application.Current.MainWindow, $"读取错误:{exc.Message}", "读取失败", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
                
            }
        }

        public static void SaveClipList()
        {
            if(ClipList.Count <1)
            {
                MessageBoxEx.Show(Application.Current.MainWindow, "没有可用于保存的工作", "保存警告", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            SaveFileDialog sfd = new();

            sfd.InitialDirectory = ((MainWindow)Application.Current.MainWindow).Mec.CurrentMediaPath;
            if (!string.IsNullOrEmpty(sfd.InitialDirectory))
                sfd.FileName = Path.GetFileNameWithoutExtension(sfd.InitialDirectory);
            else
                sfd.FileName = ClipList.FirstOrDefault(p => !string.IsNullOrEmpty(p.Name))?.Name;
            if (string.IsNullOrEmpty(sfd.FileName) && !string.IsNullOrEmpty(CurrentEditFragment.InputPath) && File.Exists(CurrentEditFragment.InputPath))
            {
                sfd.FileName = Path.GetFileNameWithoutExtension(CurrentEditFragment.InputPath);
                sfd.InitialDirectory = CurrentEditFragment.InputPath;
            }
            else if (string.IsNullOrEmpty(sfd.FileName))
            {
                IEnumerable<Fragment> fragments = ClipList.SelectMany(p => p.Fragments);
                sfd.InitialDirectory = fragments.FirstOrDefault(p => !string.IsNullOrEmpty(p.InputPath) && File.Exists(p.InputPath))?.InputPath;
                if (!string.IsNullOrEmpty(sfd.InitialDirectory))
                    sfd.FileName = Path.GetFileNameWithoutExtension(sfd.InitialDirectory);
                else
                    sfd.FileName = fragments.FirstOrDefault(p => !string.IsNullOrEmpty(p.Name))?.Name;
            }
            if (string.IsNullOrEmpty(OldSavePath) || !Directory.Exists(OldSavePath))
            {
                if (string.IsNullOrEmpty(sfd.InitialDirectory))
                    sfd.InitialDirectory = MediaElementConcrol.LastPath;
                if (string.IsNullOrEmpty(sfd.InitialDirectory))
                    sfd.InitialDirectory = Config.CacheDirectory;
                else if (File.Exists(sfd.InitialDirectory))
                    sfd.InitialDirectory = Path.GetDirectoryName(sfd.InitialDirectory);
                else if (!Directory.Exists(sfd.InitialDirectory))
                    sfd.InitialDirectory = Config.CacheDirectory;
                if (!Directory.Exists(sfd.InitialDirectory))
                    Environment.GetFolderPath(Environment.SpecialFolder.MyComputer);
            }
            else
                sfd.InitialDirectory = OldSavePath;

            sfd.DefaultExt = "*.cli";
            sfd.Filter = "剪辑信息文档(.cli)|*.cli";
            
            sfd.CheckPathExists = true;
            sfd.CreatePrompt = true;
            sfd.DereferenceLinks = true;
            sfd.OverwritePrompt = true;
            sfd.Title = "保存当前剪辑编辑";
            if (sfd.ShowDialog() ?? false)
            {
                UpdateClipsSchemeModule();
                
                File.WriteAllText(sfd.FileName!, JsonSerializer.Serialize(new SaveData(ClipList.Select(p => (SaveClipData)p).ToArray(), ClipsGroupManage.FromClipManageCreateClipsGroupSaveDatas()), options: SerializerOptions));
                OldSavePath = Path.GetDirectoryName(sfd.FileName);
            }

        }

        public static void UpdateClipsSchemeModule()
        {
            foreach (AdvancedParametersSchemeModule flow in ClipList.SelectMany(p => p.Flows))
                Config.Instance.SchemeModules.FirstOrDefault(p => p.SchemeID == flow.SchemeID)?.CopyTo(flow, false);
            foreach (AdvancedParametersSchemeModule flow in ClipList.SelectMany(p => p.AfterprocessingFlows))
                Config.Instance.SchemeModules.FirstOrDefault(p => p.SchemeID == flow.SchemeID)?.CopyTo(flow, false);
        }

        public static void CompletionVerify()
        {
            string[] NotExtensionFullPaths = ClipList.Select(p => p.OutDirectory).Distinct().SelectMany(p =>
            {
                if (!Directory.Exists(p))
                    return [];
                return Directory.GetFiles(p, "*.*", SearchOption.TopDirectoryOnly).Select(cp => Path.Combine(p, Path.GetFileNameWithoutExtension(cp)));
            }).ToArray();

            foreach(Clip item in ClipList)
            {
                if (NotExtensionFullPaths.Contains(Path.Combine(item.OutDirectory, item.Name), StringComparer.OrdinalIgnoreCase))
                    item.CompletionStatus = CompletionStatusEnum.Success;
                else item.CompletionStatus = CompletionStatusEnum.Fail;
            }
        }

        public static bool CreateNewClip(out Clip clip)
        {
            clip = new Clip();
            if (!string.IsNullOrEmpty(ClipManage.CurrentEditFragment.clip?.OutDirectory))
                clip.OutDirectory = ClipManage.CurrentEditFragment.clip.OutDirectory;
            else
            {
                using (CommonOpenFileDialog dialog = new CommonOpenFileDialog())
                {
                    dialog.Title = "选择保存路径";
                    dialog.IsFolderPicker = true;
                    dialog.InitialDirectory = !string.IsNullOrEmpty(MediaElementConcrol.LastPath) ? MediaElementConcrol.LastPath : Environment.GetFolderPath(Environment.SpecialFolder.MyComputer);
                    dialog.EnsurePathExists = true;
                    if (dialog.ShowDialog() == CommonFileDialogResult.Cancel)
                        return false;
                    clip.OutDirectory = dialog.FileName;
                }
            }
            ClipManage.ClipList.Add(clip);
            return true;
        }
        public static void NormalizationFragmentsID()
        {
            Set<int> set = new Set<int>();
            foreach (var fragment in AllFragmentEnumerable)
            {
                if (fragment.ID < 0 || set.Contains(fragment.ID))
                    fragment.ID = ClipManage.NewFragmentID();
                set.Add(fragment.ID);
            }
            
        }
        public static void UpdateModeInputIndexValues() => ClipList.ForEach(p => p.UpdateModeInputIndexValues());
        public static Fragment[] CreateCutFragments(string FilePath, IEnumerable<TimeSpan> timeSpans)
        {
            if (string.IsNullOrWhiteSpace(FilePath) || !File.Exists(FilePath))
                throw new FileNotFoundException($"路径非法,或者目标文件不存在 路径值:[{FilePath}]");
            if (!MediaInfoTools.IsGetDuration(FilePath, out TimeSpan Duration))
                throw new FileNotFoundException($"[{FilePath}] 文件无法读取,读取信息失败！");
            GetMediaInfo.MediaInfoResult info = GetMediaInfo.GetInfo(FilePath);
            SplitTools.SplitTimeRange[] timeRanges = SplitTools.SplitTimes(timeSpans.ToArray(),TimeSpan.Zero,Duration,Config.SplitMode);
            SplitTools.SplitTimeRange timeRange;
            string FileName = Path.GetFileNameWithoutExtension(FilePath);
            Tools.TrimInvalidFileNameChars(ref FileName);
            Fragment[] Results = new Fragment[timeRanges.Length];
            for (int i = 0; i < timeRanges.Length; i++)
            {
                timeRange = timeRanges[i];
                Results[i] = new Fragment()
                {
                    Name = $"Split{i.ToString("00")}",
                    StartTimeValue = Tools.GetTimeSpanToString(timeRange.StartTime),
                    EndTimeValue = Tools.GetTimeSpanToString(timeRange.EndTime),
                    InputPath = FilePath,
                    TempWritePath = $"{Path.Combine(Config.CacheDirectory, $"{FileName}_Split_{i.ToString("00")}{Path.GetExtension(FilePath)}")}",
                    IsVideoStream = info.IsVideoStream,
                    IsAudioStream = info.IsAudioStream,
                    IsSubStream = info.IsSubStream
                };
            }
            return Results;
        }

        public static void CutSplitFile(string FilePath, IEnumerable<double> SplitTimes, Clip? TargetClip = null, bool IsEnableCutToSingleClips = false)
        {
            Fragment[] fragments = CreateCutFragments(FilePath, SplitTimes.Select(p => TimeSpan.FromSeconds(p)));
            string ClipName = Path.GetFileNameWithoutExtension(FilePath);
            Tools.TrimInvalidFileNameChars(ref ClipName);
            if (IsEnableCutToSingleClips)
            {
                for (int i = 0; i < fragments.Length; i++)
                {
                    if (!CreateNewClip(out Clip clip))
                        return;
                    clip.Name = $"{ClipName}_{i.ToString("00")}";
                    clip.AddFragment(fragments[i]);
                    CurrentEditFragment.CopyToSet(clip.Fragments[0]);
                }
                DuplicateNameCheck();
            }
            else if (TargetClip != null)
                TargetClip.AddFragments(fragments);
            else
            {
                if (!CreateNewClip(out Clip clip))
                    return;
                clip.Name = ClipName;
                clip.AddFragments(fragments);
                DuplicateNameCheck();
            }
        }

        public static void ClearClipCheck(Clip clip)
        {
            try
            {
                string Path = clip.TempCacheDirectory;
                if (Directory.Exists(Path))
                    Directory.Delete(Path, true);
            }
            catch (Exception exc)
            {
                LogManage.WriteExceptionInfo($"清理临时缓存目录异常:剪辑:{clip.Name}", exc, true);
                return;
            }
        }

        public class SaveClipData
        {
            public string Name { get; set; }
            public bool EnablePriority { get; set; }
            public bool EnableSingleOut { get; set; }
            public ClipGroupInfo? GroupInfo { get; set; }
            public string? OutDirectory { get; set; }
            public Fragment[]? Fragments { get; set; }
            public AdvancedParametersSchemeModule[]? Flows { get; set; }
            public AdvancedParametersSchemeModule[]? AfterprocessingFlows { get; set; }
#pragma warning disable CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
            public SaveClipData() { }
#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
            private SaveClipData(string Name, bool EnablePriority, bool EnableSingleOut, ClipGroupInfo? GroupInfo, string? OutDirectory, Fragment[]? Fragments,
                AdvancedParametersSchemeModule[]? Flows, AdvancedParametersSchemeModule[]? AfterprocessingFlows)
            {
                this.Name = Name; this.EnablePriority = EnablePriority; this.EnableSingleOut = EnableSingleOut; this.OutDirectory = OutDirectory;
                this.Fragments = Fragments; this.Flows = Flows; this.AfterprocessingFlows = AfterprocessingFlows;
            }

            public Clip ToClip() 
            {
                Clip result = new();
                result.Name = this.Name;
                result.IsEnablePriority = this.EnablePriority;
                result.EnableSingleOut = this.EnableSingleOut;
                result.OutDirectory = this.OutDirectory ?? string.Empty;
                if (this.Fragments != null && this.Fragments.Length > 0)
                    result.Fragments = new ObservableCollection<Fragment>(this.Fragments);
                if (this.Flows != null && this.Flows.Length > 0)
                    result.Flows = new ObservableCollection<AdvancedParametersSchemeModule>(this.Flows);
                if (this.AfterprocessingFlows != null && this.AfterprocessingFlows.Length > 0)
                    result.AfterprocessingFlows = new ObservableCollection<AdvancedParametersSchemeModule>(this.AfterprocessingFlows);
                return result;
            }

            public static implicit operator SaveClipData(Clip clip) => new SaveClipData(clip.Name, clip.IsEnablePriority, clip.EnableSingleOut, clip.Group, 
                clip.OutDirectory, clip.Fragments?.ToArray(), clip.Flows?.ToArray(), clip.AfterprocessingFlows?.ToArray());
        }
    
        public record SaveData(SaveClipData[] ClipsData, ClipsGroupSaveData[]? GroupsData);
    }

}
